﻿
(function($) {

    //Utility
    function clone(obj) {
        var objClone;
        if (obj.constructor == Object) {
            objClone = new obj.constructor();
        } else {
            objClone = new obj.constructor(obj.valueOf());
        }
        for (var key in obj) {
            if (objClone[key] != obj[key]) {
                if (typeof (obj[key]) == 'object') {
                    objClone[key] = clone(obj[key]);
                } else {
                    objClone[key] = obj[key];
                }
            }
        }
        objClone.toString = obj.toString;
        objClone.valueOf = obj.valueOf;
        return objClone;
    }

    function tryeach(obj, fn, arg) {
        if (obj == null)
            return;
        else if (jQuery.isArray(obj))
            jQuery.each(obj, function() { fn.call(this, arg); });
        else
            fn.call(arg, obj);
    }

    function tryToBoolean(obj) {
        if (obj.toLowerCase && (obj.toLowerCase() == "true" || obj.toLowerCase() == "false"))
            return obj.toLowerCase() == "true";
        else
            return obj;
    }

    // treeforms.binder defined
    treeforms.binder = function() { this.initialize.apply(this, arguments); };
    treeforms.binder.instances = {};
    treeforms.binder.commitAll = function(callback) {
        jQuery.each(treeforms.binder.instances, function(name, val) {
            try { val.commit(); } catch (e) { }
        });

        // false async
        // [waiting] to correct it. by mark
        if (jQuery.isFunction(callback)) setTimeout(callback, 500);
    }
    treeforms.binder.prototype = {
        initialize: function(define) {
            jQuery.extend(this, define);
            treeforms.binder.instances[this.name] = this;
            // wait prepare then load
            if (this.prepare(this.load) != false)
                this.load();
        },
        load: function() {
            var binding = this;
            this.treeReady(onTreeReady);

            function onTreeReady() {
                binding.linkingReady(onLinkingReady);
                binding.refreshReady();
            }

            function onLinkingReady() {
                var onCommit = binding.commit;
                binding.commit = function() {
                    var commits = [];
                    jQuery.each(binding.linkings, function() {
                        commits = jQuery.merge(commits, this.commit());
                    });
                    onCommit.call(binding, jQuery.unique(commits));
                }
                binding.ready(binding.tree);
            }
        },
        treeReady: function(callback) {
            var thisObj = this;
            if (jQuery.isFunction(this.tree)) {
                this.tree(function(tree) {
                    thisObj.tree = tree;
                    callback.call(thisObj);
                });
            } else if (this.tree != null) {
                callback.call(this);
            }
        },
        linkingReady: function(callback) {

            var binding = this;
            // to map with initialize
            var linkings = this.linkings;
            this.linkings = [];
            linkings = jQuery.map(jQuery.makeArray(linkings), function(linking) {
                jQuery.each(binding.trigger, function(name, val) {
                    if (linking[name] && val)
                        linking[name] = val;
                });
                return new treeforms.binder.linking(linking, binding);
            });
            this.linkings = jQuery.merge(this.linkings, linkings);

            // to each with ready
            jQuery.each(this.linkings, function() {
                this.ready();
            });

            callback.call(this);
        },
        refreshReady: function() {
            this.refresher = new treeforms.refresher(this.refresher, this);
        },
        trigger: [],
        prepare: function() { },
        ready: function() { },
        commit: function() { }
    };

    treeforms.binder.linking = function() { this.initialize.call(this, arguments[0], arguments[1]); };
    treeforms.binder.linking.prototype = {
        initialize: function(define, binder) {
            jQuery.extend(this, define);

            var thisObj = this;
            this.binder = binder;
            this.parse(this);
            this.type = define.equivs != null ? "equiv" : define.outputs != null ? "output" : null;
            this.node = buildNode();
            this.element = buildElement();
            this.equivs = buildEquivs();
            this.outputs = buildOutputs();
            this.events = buildEvents();
            function buildNode() {
                var nodes = zXPath.selectNodes(thisObj.binder.tree, thisObj.from);

                if (thisObj.type == "equiv") {
                    for (var i = 1; i < nodes.length && (thisObj.from_in || thisObj.to_in) == null; i++) {
                        var cloneThis = clone(define);
                        cloneThis.from_in = i;
                        cloneThis.to_in = i;
                        binder.linkings.push(new treeforms.binder.linking(cloneThis, thisObj.binder));
                    }
                    return nodes[thisObj.from_in || 0];
                } else if (thisObj.type == "output") {
                    return nodes;
                }
            }
            function buildElement() {
                if (thisObj.type == "equiv") {
                    if (thisObj.to != null)
                        return $(thisObj.to, binder.container).get(thisObj.to_in || 0);
                    else
                        return $("<input type='hidden' />").get(0);
                } else if (thisObj.type == "output") {
                    return $(thisObj.to, binder.container);
                }
            }
            function buildEquivs() {
                return jQuery.map(jQuery.makeArray(thisObj.equivs), function(equiv) {
                    return new treeforms.binder.linking.equiv(equiv, thisObj);
                });
            }
            function buildOutputs() {
                return jQuery.map(jQuery.makeArray(thisObj.outputs), function(output) {
                    return new treeforms.binder.linking.output(output, thisObj);
                });
            }
            function buildEvents() {
                return jQuery.map(jQuery.makeArray(thisObj.events), function(event) {
                    return new treeforms.event(event, thisObj.element);
                });
            }
        },
        ready: function() {
            jQuery.each(jQuery.makeArray(this.equivs), function() {
                this.bind();
            });
            jQuery.each(jQuery.makeArray(this.outputs), function() {
                if (this.prepend != true) this.bind();
            });
        },
        commit: function() {
            return jQuery.unique(jQuery.map(this.equivs, function(equiv) {
                return equiv.commit();
            }));
        },
        parse: function() { }
    };

    treeforms.refresher = function() { this.initialize.call(this, arguments[0], arguments[1]); };
    treeforms.refresher.prototype = new Array();
    treeforms.refresher.prototype.initialize = function(define, binder) {
        var thisObj = this;
        jQuery.each(define || {}, function(name, val) {
            if (name != "valueOf" && jQuery.isFunction(val)) {
                thisObj.push({ selector: name, action: val });
            }
        });
        jQuery.each(jQuery.map(thisObj, function(i) {
            return $(i.selector, binder.container).bind("_refresh", i.action).bind("refresh", function() {
                $(this).triggerHandler("_refresh");
                //                var current = $(this);
                //                while (current = current.triggerHandler("_refresh"));
            });
        }), function() {
            this.each(function() { $(this).triggerHandler("refresh"); });
        });
    };

    treeforms.binder.linking.output = function() { this.initialize.call(this, arguments[0], arguments[1]); };
    treeforms.binder.linking.output.prototype = {
        initialize: function(define, linking) {
            jQuery.extend(this, define);
            this.linking = linking;
            if (this.prepend == true)
                this.bind();
        },
        nodeValue: function(node, form) {
            return tryToBoolean($(zXPath.selectSingleNode(node, form)).text());
        },
        bind: function() {
            var thisObj = this;
            if (this.empty != false) {
                if (jQuery.isFunction(this.empty))
                    this.empty(this.linking.element);
                else
                    this.linking.element.empty();
            }
            jQuery.each(jQuery.makeArray(this.linking.node), function() {
                var node = this;
                var contents = !jQuery.isFunction(thisObj.to) ? thisObj.to : thisObj.to(node);
                if (contents != null && contents != "") {
                    jQuery.each(jQuery.makeArray(thisObj.from), function(i) {
                        contents = contents.replace(new RegExp("\\{" + i + "\\}", "img"), thisObj.nodeValue(node, this));
                    });
                    thisObj.linking.element.append(contents);
                }
            });
        }
    };


    treeforms.binder.linking.equiv = function() { this.initialize.call(this, arguments[0], arguments[1]); };
    treeforms.binder.linking.equiv.prototype = {
        initialize: function(define, linking) {
            jQuery.extend(this, define);

            this.linking = linking;

            this.listening = "click";
            switch (this.linking.element.tagName.toUpperCase()) {
                case "INPUT":
                    switch (this.linking.element.type.toUpperCase()) {
                        case "TEXT":
                        case "HIDDEN":
                            this.listening = "blur"; break;
                        case "CHECKBOX":
                        case "RADIO":
                        default:
                            this.listening = "click"; break;
                    };
                    break;
                case "SELECT":
                    this.listening = "change"; break;
                case "TEXTAREA":
                    this.listening = "blur"; break;
            }

            $(this.linking.element).bind(this.listening, this, function(e) {
                e.data.commit();
            });

            if (this.doing != null) {
                var equiving = this;
                jQuery.each(jQuery.makeArray(this.doing), function() {
                    equiving[this]();
                });
            }
        },
        elementValue: function(val) {
            var result = null;
            if (jQuery.isFunction(this.to))
                result = this.to.call(this.linking.element, val);
            else {
                switch (this.to.toUpperCase()) {
                    case "VALUE":
                        if (val == "" && this.linking.element.tagName == "SELECT")
                            break;
                        result = $(this.linking.element).val(val);
                        break;
                    default:
                        result = $(this.linking.element).attr(this.to, val); break;
                }
            }
            if (val == null && this.format && this.format.to) {
                result = this.format.to(result);
            }
            //            if (val == null && this.format && this.format.to) {
            //                result = this.format.to(result);
            //            }
            return result;
        },
        nodeValue: function(val) {
            if (zXPath.selectSingleNode(this.linking.node, this.from) == null) {
                //$(this.linking.node).append($("<" + this.from + "></" + this.from + ">").get(0));
                $(this.linking.node).append(treeforms.createNode(this.from, this.linking.node));
            }

            if (jQuery.browser.msie && val != null)
                val = val.toString();

            var result = tryToBoolean($(zXPath.selectSingleNode(this.linking.node, this.from)).text(val));
            if (val == null && this.format && this.format.from) {
                result = this.format.from(result);
            }
            return result;
        },
        bind: function() {
            this.elementValue(this.nodeValue());
            //(this.linking.element.refresh || function() { })();
        },
        commit: function() {
            this.nodeValue(this.elementValue());
            return this.linking.node;
        }
    };

})(jQuery);